//
//  ViewController.m
//  threadDemo
//
//  Created by cgws on 2021/5/19.
//

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
//    performSelector...只要是NSObject的子类或者对象都可以通过调用方法进入子线程和主线程，其实这些方法所开辟的子线程也是NSThread的另一种体现方式。
//    在编译阶段并不会去检查方法是否有效存在，如果不存在只会给出警告
    
//    需要注意的是：如果是带afterDelay的延时函数，会在内部创建一个 NSTimer，然后添加到当前线程的Runloop中。
//    也就是如果当前线程没有开启runloop，该方法会失效。在子线程中，需要启动runloop(注意调用顺序)
    
    dispatch_queue_t queue = dispatch_queue_create("com.zhangm.a",NULL);
    dispatch_async(queue, ^{
//        [self performSelector:@selector(myFunction) withObject:nil afterDelay:3];
        
        [[NSRunLoop currentRunLoop] run];
    });
    
//    [self dispatchSignal];
//    [self createSema];
//    [self groupAndNotify];
//    [self dependences];
    
//    [self GCDThreadOperation];
    [self threadOperationDependecy];
}

- (void)myFunction{
    NSLog(@"来了");
}

/**
 总结：由于设定的信号值为2，先执行两个线程，等执行完一个，才会继续执行下一个，保证同一时间执行的线程数不超过2。
 假设我们设定信号值=1
 结果:线程会串行执行，执行完一个接着执行另一个。
 
 当我们多个线程要访问同一个资源的时候，往往会设置一个信号量，当信号量大于0的时候，新的线程可以去操作这个资源，操作时信号量-1，操作完后信号量+1，当信号量等于0的时候，必须等待，所以通过控制信号量，我们可以控制能够同时进行的并发数。

 在网络请求的开发中，经常会遇到两种情况，一种是我在一个界面需要同时请求多种数据，比如列表数据、广告数据等，全部请求到后再一起刷新界面。另一种是我的请求必须满足一定顺序，比如必须先请求个人信息，然后根据个人信息请求相关内容。这些要求对于普通的操作是可以做到并发控制和依赖操作的，但是对于网络请求这种需要时间的请求来说，效果往往与预期的不一样，这时候就需要用信号量来做一个控制
 
 信号量是一个整数，在创建的时候会有一个初始值，这个初始值往往代表我要控制的同时操作的并发数。在操作中，对信号量会有两种操作：信号通知与等待。信号通知时，信号量会+1，等待时，如果信号量大于0，则会将信号量-1，否则，会等待直到信号量大于0。什么时候会大于零呢？往往是在之前某个操作结束后，我们发出信号通知，让信号量+1。
 */
-(void)dispatchSignal{
    //crate的value表示，最多几个资源可访问
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 
    //任务1
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);
    });
    
    //任务2
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);
    });
    
    //任务3
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);
    });
}

- (void)createSema
{
    dispatch_semaphore_t sema = dispatch_semaphore_create(5);
    for (int i = 0;i < 100;i++) {
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            // 操作
            dispatch_semaphore_signal(sema);
            
            NSLog(@"i = :%d ;sema:%@" ,i,sema);
        });
    }
}
/**
 多个请求结束后统一操作

 假设我们一个页面需要同时进行多个请求，他们之间倒是不要求顺序关系，但是要求等他们都请求完毕了再进行界面刷新或者其他什么操作。

 这个需求我们一般可以用GCD的group和notify来做到：
 
 notify的作用就是在group中的其他操作全部完成后，再操作自己的内容，所以我们会看到上面三个内容都打印出来后，才打印界面刷新的内容。

 但是当将上面三个操作改成真实的网络操作后，这个简单的做法会变得无效，为什么呢？因为网络请求需要时间，而线程的执行并不会等待请求完成后才真正算作完成，而是只负责将请求发出去，线程就认为自己的任务算完成了，当三个请求都发送出去，就会执行notify中的内容，但请求结果返回的时间是不一定的，也就导致界面都刷新了，请求才返回，这就是无效的。

 要解决这个问题，我们就要用到上面说的信号量来操作了。

 在每个请求开始之前，我们创建一个信号量，初始为0，在请求操作之后，我们设一个dispatch_semaphore_wait，在请求到结果之后，再将信号量+1，也即是dispatch_semaphore_signal。这样做的目的是保证在请求结果没有返回之前，一直让线程等待在那里，这样一个线程的任务一直在等待，就不会算作完成，notify的内容也就不会执行了，直到每个请求的结果都返回了，线程任务才能够结束，这时候notify也才能够执行
 
 伪代码如下：

 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
 [网络请求:{
         成功：dispatch_semaphore_signal(sema);
         失败：dispatch_semaphore_signal(sema);
 }];
 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
 */

- (void)groupAndNotify
{
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求1
        NSLog(@"Request_1");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求2
        NSLog(@"Request_2");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求3
        NSLog(@"Request_3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任务均完成，刷新界面");
    });
}

/**
 多个请求顺序执行

 有时候我们需要按照顺序执行多次请求，比如先请求到用户信息，然后根据用户信息中的内容去请求相关的数据，这在平常的代码中直接按照顺序往下写代码就可以了，但这里因为涉及到多线程之间的关系，就叫做线程依赖。

 线程依赖用GCD做比较麻烦，建议用NSOperationQueue做，可以更加方便的设置任务之间的依赖。
 
 一般的多线程操作这样做是可以的，线程2会等待线程1完成后再执行。但是对于网络请求，问题又来了，同样，网络请求需要时间，线程发出请求后即认为任务完成了，并不会等待返回后的操作，这就失去了意义。

 要解决这个问题，还是用信号量来控制，其实是一个道理，代码也是一样的，在一个任务操作中：

 dispatch_semaphore_t sema = dispatch_semaphore_create(0);
 [网络请求:{
         成功：dispatch_semaphore_signal(sema);
         失败：dispatch_semaphore_signal(sema);
 }];
 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

 还是去等待请求返回后，才让任务结束。而依赖关系则通过NSOperationQueue来实现。

 */

- (void)dependences
{
    // 1.任务一：获取用户信息
    /*NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Request_01");
    }];

    // 2.任务二：请求相关数据
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Request_02");
    }];

    // 3.设置依赖
    [operation2 addDependency:operation1];// 任务二依赖任务一

    // 4.创建队列并加入任务
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation2, operation1] waitUntilFinished:NO];*/
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);//创建信号量
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);            //创建队列
    dispatch_group_t group = dispatch_group_create();                         //创建线程组
    dispatch_group_async(group, queue, ^{
                   NSLog(@"第一个网络请求");
                    dispatch_semaphore_signal(semaphore);
                });
    dispatch_group_async(group, queue, ^{
                   NSLog(@"第二个网络请求");
                    dispatch_semaphore_signal(semaphore);
                });
    dispatch_group_async(group, queue, ^{
                   NSLog(@"第三个网络请求");
                    dispatch_semaphore_signal(semaphore);
                });
    dispatch_group_notify(group, queue, ^{
                     dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                     dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                     dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
                     NSLog(@"三个请求完成后执行");
                });
}

#pragma mark - GCDThreadOperation

/**
 注意，这里用到的dispatch_barrier_async如果使用的队列是dispatch_global_queue，那么就等同意dispatch_async，起不到阻塞的作用。我们需要自己创建并发队列，然后再执行barrier函数，前面ABC三个任务随机，后面DE随机，但是DE的执行必须是等待ABC任务执行完的。
 */
- (void)GCDThreadOperation
{
//    dispatch_barrier_async

    dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
        dispatch_async(queue, ^{
            NSLog(@"任务A");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务B");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务C");
        });
        
        dispatch_barrier_async(queue, ^{
            NSLog(@"阻塞自定义并发队列");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务D");
        });
        
        dispatch_async(queue, ^{
            NSLog(@"任务E");
        });
}

#pragma mark - NSBlockOperation 操作线程依赖
- (void)threadOperationDependecy
{

    NSBlockOperation *operatioon1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务A");
    }];
    
    NSBlockOperation *operatioon2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务B");
    }];
    
    NSBlockOperation *operatioon3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务C");
    }];
    
    NSBlockOperation *operatioon4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"任务D");
    }];
    
    [operatioon4 addDependency:operatioon1];
    [operatioon4 addDependency:operatioon2];
    [operatioon4 addDependency:operatioon3];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operatioon1,operatioon2,operatioon3,operatioon4] waitUntilFinished:YES];
    NSLog(@"完成之后的操作");
}
@end
